<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      //参考文章：https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
      // (macro)task（又称之为宏任务），可以理解是每次执行栈执行的代码就是一个宏任务（包括每次从事件队列中获取一个事件回调并放到执行栈中执行）
      // 浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行，会在一个(macro)task执行结束后，在下一个(macro)task 执行开始前，对页面进行重新渲染，流程如下(macro)task->渲染->(macro)task->...
      // (macro)task主要包含：script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
      // microtask（又称为微任务），可以理解是在当前 task 执行结束后立即执行的任务。也就是说，在当前task任务后，下一个task之前，在渲染之前。
      // 所以它的响应速度相比setTimeout（setTimeout是task）会更快，因为无需等渲染。也就是说，在某一个macrotask执行完后，就会将在它执行期间产生的所有microtask都执行完毕（在渲染前）。
      // microtask主要包含：Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
      // 运行机制
      //1.执行一个宏任务（栈中没有就从事件队列中获取）
      //2.执行过程中如果遇到微任务，就将它添加到微任务的任务队列中
      //3.宏任务执行完毕后，立即执行当前微任务队列中的所有微任务（依次执行）
      //4.当前宏任务执行完毕，开始检查渲染，然后GUI线程接管渲染
      //5.渲染完毕后，JS线程继续接管，开始下一个宏任务（从事件队列中获取）

      //Promise和async中的立即执行
      // 我们知道Promise中的异步体现在then和catch中，所以写在Promise中的代码是被当做同步任务立即执行的。
      // 而在async/await中，在出现await出现之前，其中的代码也是立即执行的。那么出现了await时候发生了什么呢？wait做了什么
      //从字面意思上看await就是等待，await 等待的是一个表达式，这个表达式的返回值可以是一个promise对象也可以是其他值。
      //很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码，实际上await是一个让出线程的标志。
      //await后面的表达式会先执行一遍，将await后面的代码加入到microtask中，然后就会跳出整个async函数来执行后面的代码。
      //由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask
      async function async1() {
        console.log("async1 start");
        await async2();
        console.log("async1 end");
      }
      //等价于
      async function async1() {
        console.log("async1 start");
        Promise.resolve(async2()).then(() => {
          console.log("async1 end");
        });
      }

      async function async1() {
        console.log("async1 start");
        await async2(); //await是一个让出线程的标志，await后面的表达式会先执行一遍，将await后面的代码加入到microtask中，然后就会跳出整个async函数来执行后面的代码
        console.log("async1 end"); //microtask
      }

      async function async2() {
        console.log("async2");
      }

      console.log("script start");

      setTimeout(function () {
        console.log("setTimeout");
      }, 0);

      async1();

      new Promise(function (resolve) {
        console.log("promise1");
        resolve();
      }).then(function () {
        console.log("promise2");
      });

      console.log("script end");

      //js事件循环
      /**
      为了解决一个任务耗时过长，后一个任务必须等着的问题，把任务分为两类：同步任务和异步任务
      1.同步和异步任务分别进入不同的执行场所，同步的进入主线程，异步的进入Event Table并注册函数
      2.当指定的事情完成时，Event Table会将这个函数移入Event Queue中
      3.主线程内的任务执行完毕为空，会去Event Queue读取对应的函数，进入主线程执行
      4.上述过程会不断重复，也就是常说的Event Loop(事件循环)
      比如：
      let data = [];
      $.ajax({
          url:www.javascript.com,
          data:data,
          success:() => {
              console.log('发送成功!');
          }
      })
       console.log('代码执行结束');
       1.ajax进入Event Table并注册回调函数success
       2.执行console.log('代码执行结束')
       3.ajax事件完成，回调函数success进入Event Queue
       4.主线程从Event Queue读取回调函数success并执行


       再比如：
       setTimeout(() => {
         task()
       },3000)
       sleep(10000000)
       发现控制台执行task()需要的时间远远超过3秒，说好的延时三秒，为啥现在需要这么长时间啊？
       1.task()进入Event Table并注册，计时开始
       2.执行sleep函数，很慢，非常慢，计时仍在继续
       3.3秒到了，计时事件setTimeout完成，task()进入Event Queue，但是sleep太慢了，还没执行完，只好等着
       4.sleep终于执行完了，task()终于从Event Queue进入了主线程执行
       上述的流程走完，我们知道setTimeout这个函数，是经过指定时间后，把要执行的任务(本例中为task())加入到Event Queue中，
       又因为是单线程任务要一个一个执行，如果前面的任务需要的时间太久，那么只能等着，导致真正的延迟时间远远大于3秒。


      setTimeout(fn,0)的含义是，指定某个任务在主线程最早可得的空闲时间执行，意思就是不用再等多少秒了，只要主线程执行栈内的同步任务全部执行完成，栈为空就马上执行。
      对于setInterval(fn,ms)来说，我们已经知道不是每过ms秒会执行一次fn，而是每过ms秒，会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms，那么就完全看不出来有时间间隔了。

      以上都是广义的同步任务和异步任务，对任务还有更精细的定义：
      1.macro-task(宏任务)：包括整体代码script，setTimeout，setInterval
      2.micro-task(微任务)：Promise，process.nextTick
      不同类型的任务会进入对应的Event Queue，比如setTimeout和setInterval会进入相同的Event Queue
      事件循环的顺序，决定js代码的执行顺序。进入整体代码(宏任务)后，开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始，找到其中一个任务队列执行完毕，再执行所有的微任务。


      再比如：
console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
第一轮事件循环流程分析如下:
1.整体script作为第一个宏任务进入主线程，遇到console.log，输出1
2.遇到setTimeout，其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
3.遇到process.nextTick()，其回调函数被分发到微任务Event Queue中。我们记为process1
4.遇到Promise，new Promise直接执行，输出7。then被分发到微任务Event Queue中。我们记为then1
5.又遇到了setTimeout，其回调函数被分发到宏任务Event Queue中，我们记为setTimeout2
6.第一轮事件循环宏任务结束后，我们发现了process1和then1两个微任务
7.执行process1,输出6
8.执行then1，输出8
第一轮事件循环正式结束，这一轮的结果是输出1，7，6，8。

第二轮事件循环从setTimeout1宏任务开始：
1.首先输出2，接下来遇到了process.nextTick()，同样将其分发到微任务Event Queue中，记为process2
2.new Promise立即执行输出4，then也分发到微任务Event Queue中，记为then2
3.第二轮事件循环宏任务结束后，我们发现了process2和then2两个微任务可以执行
4.执行process2,输出3
5.执行then2，输出5
第二轮事件循环结束，第二轮输出2，4，3，5。

第三轮事件循环从setTimeout2宏任务开始：
1.直接输出9
2.将process.nextTick()分发到微任务Event Queue中,记为process3
3.直接执行new Promise，输出11
4.将then分发到微任务Event Queue中，记为then3
5.第三轮事件循环宏任务执行结束，执行两个微任务process3和then3
6.执行process3,输出10
7.执行then3，输出12
第三轮事件循环结束，第三轮输出9，11，10，12

总结：事件循环是js实现异步的一种方法，也是js的执行机制
      javascript是一门单线程语言


     */
    </script>
  </body>
</html>
